#include "precompiled.hpp"
#include "exec_context.hpp"
#include "interpreter.hpp"
#include "native.hpp"

///////////////////////////////////////////////////////////////////////////////////////
////// JAM module
///////////////////////////////////////////////////////////////////////////////////////
void jam_module::init_hash()
{
	jam_string emp(boost::cref("#"),1);
	vars_.set_empty_key(emp);vars_.set_deleted_key(emp);
	rules_.set_empty_key(emp);rules_.set_deleted_key(emp);
	imported_names_.set_empty_key(emp);imported_names_.set_deleted_key(emp);
}

const jam_list& jam_module::get_var_value(const jam_string &name,bool only_local) const
{
	var_hash_t::const_iterator pos=vars_.find(name);
	if (pos==vars_.end())
	{
		if (only_local || !is_user_module_)
			return null_list;

		//Only support globals search in user modules
		if (is_user_module_)
			return context_->get_root_module()->get_var_value(name,true);
	}
	return pos->second;
}

jam_list& jam_module::get_var_value_ref(const jam_string &name)
{
	return vars_[name];
}

void jam_module::set_var_value(const jam_string &name,const jam_list &val,
	assignment_operation oper)
{
	switch(oper)
	{
	case ASSIGN_SET:
		//Replace value
		vars_[name]=val;
	break;

	case ASSIGN_APPEND:
	{
		var_hash_t::iterator pos=vars_.find(name);
		//Append to the existing value
		if (pos!=vars_.end())
			pos->second.append(val.begin(),val.end());
		else
			vars_[name]=val;
	}
	break;

	case ASSIGN_DEFAULT:
		//Set only if unset
		if (vars_.find(name)==vars_.end())
			vars_[name]=val;
	break;
	default:
		assert(false); //@nc
	}
}

void jam_module::set_single_var_value(const jam_string &name,const jam_string &val, 
	assignment_operation oper)
{
	switch(oper)
	{
	case ASSIGN_SET:
		//Replace value
		vars_[name]=val;
	break;

	case ASSIGN_APPEND:
	{
		var_hash_t::iterator pos=vars_.find(name);
		//Append to the existing value
		if (pos!=vars_.end())
			pos->second.push_back(val);
		else
			vars_[name]=val;
	}
	break;

	case ASSIGN_DEFAULT:
		//Set only if unset
		if (vars_.find(name)==vars_.end())
			vars_[name]=val;
	break;
	default:
		assert(false); //@nc
	}

}

/* Looks for a rule in the specified module, and returns it, if found.
First checks if the rule is present in the module's rule table.
Second, if name of the rule is in the form name1.name2 and name1 is in 
the list of imported modules, look in module 'name1' for rule 'name2'.
*/
bool jam_module::find_rule(const jam_string &name, jam_module::rule_ent *res,
	bool exported_search)
{
	jam_module::rule_hash::const_iterator f=rules_.find(name);
	if (f!=rules_.end())
	{
		//Only return exported rules during exported search.
		if (!exported_search || f->second.is_exported_)
			return *res=f->second,true;
	}

	if (!exported_search)
	{
		//Try splitting the name into module and rule. */
		size_t p=name.find('.');		
		if (p!=jam_string::npos)
		{
			jam_string mod=name.ref_substr(0,p);
			jam_string rulename=name.ref_substr(p+1);
			
			//Now, mod keeps the module name, and rulename keeps the rule name.
			module_hash::const_iterator pos=imported_names_.find(mod);
			if (pos!=imported_names_.end())
				return pos->second->find_rule(rulename,res,true);
		}
	}

	return false;
}

void jam_module::import_rule(jam_rule_ptr rule, const jam_string &name, bool localize)
{
	rule_ent &ent=rules_[name];
	ent.rule_=rule;
	ent.is_localized_=localize;	
	//this rule is really part of some other module; 
	//just refer to it here, but don't let it out
	ent.is_exported_=false;
}

bool jam_module::export_rule(const jam_string &name)
{
	rule_hash::iterator f=rules_.find(name);
	if (f==rules_.end()) return false;

	assert(!f->second.is_localized_);
	f->second.is_exported_=true;
	return true;
}

void jam_module::register_rule(jam_rule_ptr rule,bool is_exported)
{	
//	assert(rules_.find(rule->get_name())==rules_.end());
//  Apparntely, JAM allows transparent redefinition of rules.
	rule_ent &ent=rules_[rule->get_name()];
	ent.rule_=rule;
	ent.is_exported_=is_exported;
	ent.is_localized_=false;
}

void jam_module::import_module(jam_module* module)
{
	imported_names_[module->get_name()]=module;
}

void jam_module::clear()
{
	vars_.clear();
	rules_.clear();
	imported_names_.clear(); //TODO: is this OK?
}

///////////////////////////////////////////////////////////////////////////////////////
////// Var saver
///////////////////////////////////////////////////////////////////////////////////////
var_saver::var_saver() : saved_var_place()
{
}

void var_saver::set_val(jam_list *val)
{
	assert(!saved_var_place);
	new((void*)get_list()) jam_list(*val);
	saved_var_place=val;
}

var_saver::var_saver(jam_list *val)
{
	if (val)
	{
		new((void*)get_list()) jam_list(*val);
		saved_var_place=val;
	}
	else
		saved_var_place=0;
}

var_saver::~var_saver()
{
	if (saved_var_place)
	{
		*saved_var_place=*get_list();
		get_list()->~jam_list();
	}
}

///////////////////////////////////////////////////////////////////////////////////////
////// JAM frame
///////////////////////////////////////////////////////////////////////////////////////
jam_frame::jam_frame(jam_context &context, jam_module_ptr root, const parse_node_base * node) :
	context_(context), prev_(), prev_user_(), node_(node), cur_node_(node),
	module_(root)
{
	scope_name_="module ('"+root->get_name()+"') scope";
}

jam_frame::jam_frame(jam_context &context, jam_frame *prev, const parse_node_base *node) :
	context_(context), prev_(prev), prev_user_(prev->prev_user_), 
	node_(node), cur_node_(node), module_(prev->module_), scope_name_(prev->scope_name_)
{
}

///////////////////////////////////////////////////////////////////////////////////////
////// Jam context
///////////////////////////////////////////////////////////////////////////////////////
jam_context::jam_context() : out_(&std::cout), err_(&std::cerr)
{
	jam_string emp(boost::cref("#"),1);
	modules_.set_empty_key(emp);modules_.set_deleted_key(emp);

	//Register built-in rules
	create_natives_for_context(this);
}

void jam_context::set_streams(std::ostream *out,std::ostream *err)
{
	out_=out; err_=err;
}

jam_module_ptr jam_context::bind_module(const jam_string &name, bool is_user)
{
	jam_module_ptr &entry=modules_[name];
	if (!entry)
		entry=new jam_module(this,is_user,name);
	return entry;
}

boost::intrusive_ptr<jam_context> jam_context::new_context()
{
	return new jam_context();
}

jam_module_ptr jam_context::get_root_module()
{
	return bind_module(JAM_ROOT_MODULE_NAME);
}

bool jam_context::bind_rule(const jam_string &name, jam_module_ptr module, 
	jam_module::rule_ent *res)
{
	if (module && module->find_rule(name,res)) return true;
	
	return get_root_module()->find_rule(name,res);
}

///////////////////////////////////////////////////////////////////////////////////////
////// Interpreting
///////////////////////////////////////////////////////////////////////////////////////
void jam_context::intepret_tree(const parse_node_ptr &node, jam_module_ptr module,
	jam_list *res)
{
	jam_interpeter interp(this);
	interp.intepret_tree(node.get(),module,res);	
}

void jam_context::intepret_tree(const parse_node_ptr &node, jam_module_ptr module, 
	jam_frame *frame, jam_list *res)
{
	jam_interpeter interp(this);
	interp.intepret_tree(node.get(),module,frame,res);
}

///////////////////////////////////////////////////////////////////////////////////////
////// Jam debug support
///////////////////////////////////////////////////////////////////////////////////////
void jam_context::backtrace(const jam_frame *frame)
{
	if (!frame) return;
	while (frame=frame->prev_)
	{		
		//Skip the root frame
		backtrace_line(frame);
	}
}

void jam_context::backtrace_line(const jam_frame *frame)
{
	if (!frame)
		*err_<<"(no frame):";
	else
	{
		print_source_line(*frame->cur_node_);
		*err_<<" in "<<frame->scope_name_<<std::endl;
	}
}

void jam_context::print_source_line(const parse_node_base &p)
{	
	const jam_string &file=p.file_;
	int line=p.line_;
	
	if (line <= 0)
		*err_<<"(builtin):";
	else
		*err_<<file<<":"<<line;;
}
